import java.io.*;
import java.util.*;

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;

import alice.tuprolog.*;
import antlr.non_ho_niente_da_mettermiBaseVisitor;
import antlr.non_ho_niente_da_mettermiLexer;
import antlr.non_ho_niente_da_mettermiParser;
import antlr.non_ho_niente_da_mettermiParser.*;


public class non_ho_niente_da_mettermiVisitor extends non_ho_niente_da_mettermiBaseVisitor<Void> 
{
	private IErrorHandler errorReporter = null;

	public void setErrorReporter(IErrorHandler errorReporter) 
	{
		this.errorReporter = errorReporter;
	}
	
	String basicRules = "delete_sub_list([H|T],L,Z):- delete(H,L,FL), delete_sub_list(T,FL,Z).\n"+
			"delete_sub_list([],L,L).\n\n"+
			"%exclude\n"+
			"category_filter_exclude([H|T]) :- retractall(cloth(H,_,_,_,_,_,_)),!, category_filter_exclude(T).\n"+
			"category_filter_exclude([]).\n\n"+
			"%include _only\n"+
			"retract_categories([H|T]) :- retractall(cloth(H,_,_,_,_,_,_)),  retract_categories(T).\n"+
			"retract_categories([]).\n"+
			"category_filter_include(IncludeList,AllCategories):- delete_sub_list(IncludeList,AllCategories,CatList),\n"+ 
			"  retract_categories(CatList).\n\n"+
			"% Retract che funziona anche in backtracking\n"+							
			"backtracking_retractall([H|T]):- retract(H), backtracking_retractallr(T).\n"+
			"backtracking_retractallr([H|T]):- retract(H), backtracking_retractallr(T).\n"+
			"backtracking_retractallr([]).\n"+
			"backtracking_retractall([H|T]):- asserta(H), backtracking_retractalla(T).\n"+
			"backtracking_retractalla([H|T]):- asserta(H), backtracking_retractalla(T).\n"+
			"backtracking_retractalla([]) :- fail.\n\n"+	
			"createOutfit(Season, N1, N2, N3, P1, P2, P3) :- \n"+
			"  category_filter(general),\n"+
			"  cloth(C1, N1, CO1, L1, Season, E1, P1),\n"+ 
			"  cloth(C2, N2, CO2, L2, Season, E2, P2),\n"+
			"  cloth(C3, N3, CO3, L3, Season, E3, P3),\n"+
			"  attributes_filter(C1, N1, CO1, L1, Season, E1),\n"+
			"  attributes_filter(C2, N2, CO2, L2, Season, E2),\n"+
			"  attributes_filter(C3, N3, CO3, L3, Season, E3).\n";

	String databaseName = "";
	Prolog engine = new Prolog();
	static Theory t;
	Hashtable<String, String> colors = new Hashtable<String, String>();

	String season = "";
	String list = null;
	String event = null;
	String filterType = null;
	Boolean generalFilterOn = false;
	Boolean categoryFilterOn = false;
	String allCateogiresString = "";
	String clothConstraints = "";
	String finalString = "";
	Set<String> definedAttributes = new TreeSet<String>();
	List<String> category = new ArrayList<String>();
	List<String> allCategories = new ArrayList<String>();
	String content = null;
	String wantedSeason, wantedEvent = null;
	Map<String, String> allrules = new HashMap<String, String>();
	String chosenEventRules = null;
	ArrayList<String> splitedlist = new ArrayList<String>();
	static Set<String> outfitSet = new HashSet<String>();
	
	// Qui ci sono le categorie per le quali è stata specificata una regola custom quindi bisogna togliere quella generale (attributes_filter(CAT,_,_,_,_,_))
	HashMap<String, ArrayList<String>> removeCategoryRule = new HashMap<String, ArrayList<String>>();


	@Override
	public Void visitScope(ScopeContext ctx) 
	{
		super.visitScope(ctx);

		// Qui mettiamo anche tutti i fatti attributes_filter(CATEGORY,_,_,_,_,_).
		for(String s:allCategories)
		{
			if(!removeCategoryRule.get(season+event).contains(s))
				chosenEventRules += "attributes_filter("+s+",_,_,_,_,_).\n";
		}

		chosenEventRules = chosenEventRules +"\n"+ content + basicRules;
		System.out.println(chosenEventRules);
		engine.clearTheory();
		Prolog engine2 = new Prolog();

		try 
		{
			outfitSet.clear();
			t = new Theory(chosenEventRules);
			engine.setTheory(t);
			engine2.setTheory(t);
			SolveInfo info = engine.solve("createOutfit("+wantedSeason+", N1, N2, N3, top, bottom, footwear).");

			Term name1,name2,name3;
			while(info.hasOpenAlternatives())
			{
				name1 = info.getVarValue("N1");
				name2 = info.getVarValue("N2");
				name3 = info.getVarValue("N3");
				String outfit = getOutputString(engine2, name1.toString());
				outfit += getOutputString(engine2, name2.toString());
				outfit += getOutputString(engine2, name3.toString());
				outfitSet.add(outfit);
				info = engine.solveNext();

			}
			engine.setTheory(t);
			SolveInfo info1 = engine.solve("createOutfit("+wantedSeason+", N1, N2, N3, coat, overall, footwear).");
			while(info1.hasOpenAlternatives())
			{
				name1 = info1.getVarValue("N1");
				name2 = info1.getVarValue("N2");
				name3 = info1.getVarValue("N3");
				String outfit = getOutputString(engine2, name1.toString());
				outfit += getOutputString(engine2, name2.toString());
				outfit += getOutputString(engine2, name3.toString());
				outfitSet.add(outfit);
				info1 = engine.solveNext();

			}
			//resultPrint();
		}
		catch (Exception e) 
		{
			e.printStackTrace();
			throw new VisitorException(e);
		}
//		catch (MalformedGoalException e) 
//		{
//			e.printStackTrace();
//		} 
//		catch (NoMoreSolutionException e) 
//		{
//			e.printStackTrace();
//		}
//		catch (NoSolutionException e) 
//		{
//			e.printStackTrace();
//		}

		return null;
	}

	private String getOutputString(Prolog engine2, String name) throws MalformedGoalException, NoSolutionException {
		SolveInfo si = engine2.solve("cloth(_, "+name+", C, L, S, E, _).");
		String color1 = si.getVarValue("C").toString();
		String length1 = si.getVarValue("L").toString();
		String season1 = si.getVarValue("S").toString();
		String extra1 = si.getVarValue("E").toString();
		String outfit = "Name: " + name.toString() +"\n" +
						"   Color: " + color1+ "\n"+
						"   Length: "+ length1+ "\n"+
						"   Season: "+ season1+"\n"+
						"   Extra: "+extra1+"\n";
		return outfit;
	}

	@Override //DONE
	public Void visitCloset(ClosetContext ctx)
	{
		super.visitCloset(ctx);
		databaseName = ctx.PATH().getText();
		//System.out.println(databaseName);
		try 
		{ 
			content = new Scanner(new File(databaseName.replace("\"", ""))).useDelimiter("\\Z").next();
			t = new Theory(content + "\n" + basicRules);	
			engine.setTheory(t);
			SolveInfo info = engine.solve("cloth(Cat,_,_,_,_,_,_).");
			Set<String> catSet = new HashSet<String>();
			while(info.hasOpenAlternatives())
			{
				Term cat = null;
				cat = info.getVarValue("Cat");
				catSet.add(cat.toString());
				info =engine.solveNext();
			}
			for(String el: catSet){
				allCategories.add(el);
			}
			//System.out.println(catSet.toString());
			allCateogiresString = "categories("+catSet.toString()+").\n";

		}

		catch (FileNotFoundException e) 
		{
			errorReporter.reportError("Line "+ctx.PATH().getSymbol().getLine()+": the closet DB file was not found. Check if it exists on the file system.");
			e.printStackTrace();
			throw new VisitorException("Line "+ctx.PATH().getSymbol().getLine()+": the closet DB file was not found. Check if it exists on the file system.");
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
			throw new VisitorException(e);
		}
		catch (InvalidTheoryException e) 
		{
			errorReporter.reportError("Line "+ctx.PATH().getSymbol().getLine()+": the DB is not valid.");
			e.printStackTrace();
			throw new VisitorException("Line "+ctx.PATH().getSymbol().getLine()+": the DB is not valid.");
		} 
		catch (MalformedGoalException e1)
		{
			e1.printStackTrace();
			throw new VisitorException(e1);
		}		
		catch (NoSolutionException e) 
		{
			e.printStackTrace();
			throw new VisitorException(e);
		}
		catch (NoMoreSolutionException e) 
		{
			e.printStackTrace();
			throw new VisitorException(e);
		}

		return null;
	}

	@Override //DONE
	public Void visitList(ListContext ctx) 
	{
		super.visitList(ctx);
		splitedlist.clear();
		list = ctx.getText();
		String prova = list.substring(1, list.length()-1);
		for(String el : prova.split("\\,"))
			splitedlist.add(el);

		return null;
	}

	@Override //DONE
	public Void visitColor_list(Color_listContext ctx) 
	{
		super.visitColor_list(ctx);
		colors.put(ctx.STRING().getText(),list);

		//System.out.println(ctx.getChildCount());

		return null;
	}
	@Override
	public Void visitEvent(EventContext ctx) 
	{	

		season = ctx.STRING(0).getText();
		event = ctx.STRING(1).getText();
		finalString = "";
		removeCategoryRule.put(season+event, new ArrayList<String>());
		super.visitEvent(ctx);

		//System.out.println(category);
		allrules.put(season+event, allCateogiresString + finalString);

		return null;
	}

	@Override
	public Void visitBlock(BlockContext ctx) 
	{
		generalFilterOn=false;
		super.visitBlock(ctx);

		return null;
	}

	@Override
	public Void visitCategory_Rule(Category_RuleContext ctx) 
	{	

		definedAttributes.clear();
		category.remove(ctx.STRING().getText());
		categoryFilterOn=false;
		clothConstraints = "";
		clothConstraints += "attributes_filter("+ ctx.getChild(1).getText()+ ",Name,Color,Length,Season,Extra):- !, cloth("+ ctx.getChild(1).getText()+", Name,Color,Length,Season,Extra,Position)"; 
		super.visitCategory_Rule(ctx);
		removeCategoryRule.get(season+event).add(ctx.getChild(1).getText());
		finalString = clothConstraints + ".\n" + finalString;
		clothConstraints = "";
		//System.out.println(finalString);

		return null;
	}
	@Override
	public Void visitStatement(StatementContext ctx) 
	{	
		super.visitStatement(ctx);

		return null;
	}

	@Override
	public Void visitFilter(FilterContext ctx) 
	{
		super.visitFilter(ctx);
		if(ctx.INCLUDE()!=null)
		{
			filterType = ctx.INCLUDE().toString();
			//System.out.println(filterType);
		}
		if(ctx.EXCLUDE()!=null)
		{
			filterType = ctx.EXCLUDE().toString();
			//System.out.println(filterType);
		}

		// Qui gestisco i filtri per le categorie
		if(ctx.parent.parent instanceof Category_RuleContext) 
		{
			//			String cat = ctx.parent.parent.getChild(1).toString();

			if(categoryFilterOn==false)
			{
				categoryFilterOn = true;

				if (filterType.equalsIgnoreCase("include"))
				{
					// Qui devo appendere backtracking_retractall([...])
					// e dentro la lista ci devono essere tanti attributes_filter(CAT,_,_,_,_,_). quanti sono le catogorie da eliminare

					clothConstraints += ", backtracking_retractall([";
					String delim = "";
					for(String cate : allCategories){
						if(!splitedlist.contains(cate)){
							clothConstraints += delim;
							delim=",";
							clothConstraints += "attributes_filter(" + cate + ",_,_,_,_,_)";
						}
					}

					clothConstraints += "])";


					//					for(String s:splitedlist)
					//						category.add(s);
					//					finalString += "category_filter("+cat+"):- !,categories(L), category_filter_include(" +list+ ",L).\n";
				}
				else
				{
					clothConstraints += ", backtracking_retractall([";
					String delim = "";
					for(String cate : splitedlist){
						clothConstraints += delim;
						delim=",";
						clothConstraints += "attributes_filter(" + cate + ",_,_,_,_,_)";
					}

					clothConstraints += "])";

					//					for(String s:splitedlist)
					//						allCategories.remove(s);
					//					category = allCategories;
					//					finalString += "category_filter("+cat+"):- !,category_filter_exclude("+list+").\n";
				}
			}
			else
			{
				errorReporter.reportError((String) ("Line "+(ctx.INCLUDE()!=null ? ctx.INCLUDE().getSymbol().getLine() : ctx.EXCLUDE().getSymbol().getLine())+"; Only one filter permitted inside a category rule.\n"));
				throw new VisitorException((String) ("Line "+(ctx.INCLUDE()!=null ? ctx.INCLUDE().getSymbol().getLine() : ctx.EXCLUDE().getSymbol().getLine())+"; Only one filter permitted inside a category rule.\n"));
			}

		}else if(ctx.parent.parent instanceof BlockContext) {
			// Qui gestisco i filtri general --> OK

			if(generalFilterOn==false)
			{
				generalFilterOn = true;

				if (filterType.equalsIgnoreCase("include"))
				{
					for(String s:splitedlist)
						category.add(s);

					finalString += "category_filter(general):- !,categories(L), category_filter_include(" +list+ ",L).\n";
				}
				else
				{
					ArrayList<String> allCategoriesCopy = new ArrayList<String>(allCategories.size());
					for(String el : allCategories)
						allCategoriesCopy.add(el);
					
					for(String s:splitedlist)
						allCategoriesCopy.remove(s);
					category = allCategoriesCopy;
					finalString += "category_filter(general):- !,category_filter_exclude("+list+").\n";
				}
			}
			else
			{
				errorReporter.reportError((String) ("Line "+(ctx.INCLUDE()!=null ? ctx.INCLUDE().getSymbol().getLine() : ctx.EXCLUDE().getSymbol().getLine())+"; Only one general filter permitted.\n"));
				throw new VisitorException((String) ("Line "+(ctx.INCLUDE()!=null ? ctx.INCLUDE().getSymbol().getLine() : ctx.EXCLUDE().getSymbol().getLine())+"; Only one general filter permitted.\n"));
			}
		}
		//System.out.println(category);

		return null;
	}

	// Dovrebbe essere a posto con la semantica
	@Override
	public Void visitExpression(ExpressionContext ctx) 
	{
		super.visitExpression(ctx);
		if(ctx.IS()!=null)
		{
			if(ctx.NOT()!=null)
			{
				// Caso regola IS NOT
				if (ctx.STRING(0).getText().equalsIgnoreCase("extra"))
				{
					clothConstraints += ",not(member("+ ctx.STRING(1).getText() +","+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+"))";
				}
				else
				{
					clothConstraints += ","+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+" \\= "+ctx.STRING(1).getText();
				}
			}
			else
			{
				// Caso regola IS
				if(definedAttributes.contains(ctx.STRING(0).getText()))
				{
					// C'è già una regola IS o IN per questo attributo quindi errore!!
					errorReporter.reportError("Line "+ctx.STRING(0).getSymbol().getLine()+": There is already an IS or IN rule for this attribute."+
							"\nYou can replace it with an IN rule putting the values in a list. ");
					throw new VisitorException("Line "+ctx.STRING(0).getSymbol().getLine()+": There is already an IS or IN rule for this attribute."+
							"\nYou can replace it with an IN rule putting the values in a list. ");
				}

				definedAttributes.add(ctx.STRING(0).getText());
				if (ctx.STRING(0).getText().equalsIgnoreCase("extra"))
				{
					clothConstraints += ",member("+ ctx.STRING(1).getText() +","+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+")";
				}
				else
				{
					clothConstraints +=  ","+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+" == "+ctx.STRING(1).getText();
				}
			}
		}
		else if (ctx.IN()!=null)
		{
			if(ctx.NOT()!=null)
			{
				// Regola NOT IN

				if(ctx.STRING(1)!=null)
				{
					// Caso in cui c'è una stringa
					ctx.getChild(3).accept(this);
					clothConstraints += ",not(member("+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+","+colors.get(ctx.STRING(1).getText())+"))";
				}
				else
				{
					// Caso in cui c'è una lista
					ctx.getChild(3).accept(this);
					if(ctx.STRING(0).getText().equalsIgnoreCase("extra"))
					{
						clothConstraints += ",subListNeg("+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+","+ list+")";
					}
					else
						clothConstraints += ",not(member("+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+","+list+"))";
				}
			}
			else
			{
				// Regola IN
				if(definedAttributes.contains(ctx.STRING(0).getText()))
				{
					// C'è già una regola IS o IN per questo attributo quindi errore!!
					errorReporter.reportError("Line "+ctx.STRING(0).getSymbol().getLine()+": There is already an IS or IN rule for this attribute."+
							"\nYou can replace it with an IN rule putting the values in a list. ");
					throw new VisitorException("Line "+ctx.STRING(0).getSymbol().getLine()+": There is already an IS or IN rule for this attribute."+
							"\nYou can replace it with an IN rule putting the values in a list. ");
				}

				definedAttributes.add(ctx.STRING(0).getText());

				if(ctx.STRING(1)!=null)
				{
					clothConstraints += ",member("+ctx.STRING(0).getText().substring(0, 1).toUpperCase()+ctx.STRING(0).getText().substring(1)+","+colors.get(ctx.STRING(1).getText())+")";
				}
				else
				{
					ctx.getChild(3).accept(this);
					if(ctx.STRING(0).getText().equalsIgnoreCase("extra"))
					{
						clothConstraints += ",subListPos("+ ctx.STRING(0).getText().toUpperCase()+","+ list+")";
					}
					else
						clothConstraints += ",member("+ ctx.STRING(0).toString().substring(0,1).toUpperCase()+ctx.STRING(0).toString().substring(1)+","+list+")";
				}
			}
		}

		return null;
	}


	@Override
	public Void visitOutfit_ideas(Outfit_ideasContext ctx) 
	{
		super.visitOutfit_ideas(ctx);
		wantedSeason = ctx.STRING(0).toString();
		wantedEvent = ctx.STRING(1).toString();

		if(allrules.containsKey(wantedSeason+wantedEvent))
		{
			chosenEventRules = allrules.get(wantedSeason+wantedEvent);
		}
		else 
		{
			errorReporter.reportError("Line "+ctx.STRING(0).getSymbol().getLine()+": There are no rules for this event and/or season.\n");
			throw new VisitorException("Line "+ctx.STRING(0).getSymbol().getLine()+": There are no rules for this event and/or season.\n");
		}
		return null;
	}
	/**
	 * @param args
	 * @throws IOException 
	 * @throws InvalidTheoryException 
	 */
	public static void main(String[] args) throws IOException 
	{
		/*String inputFile = null;
        if ( args.length>0 ) inputFile = args[0];
        InputStream is = System.in;
        if ( inputFile!=null ) {
            is = new FileInputStream(inputFile);
        }*/

		// clear theory, set theory con

		ANTLRInputStream input = new ANTLRInputStream(new FileInputStream("/Users/lorena_q88/provaesame"));
		non_ho_niente_da_mettermiLexer lexer = new non_ho_niente_da_mettermiLexer(input);
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		non_ho_niente_da_mettermiParser parser = new non_ho_niente_da_mettermiParser(tokens);
		ParseTree tree = parser.scope();

		non_ho_niente_da_mettermiVisitor loader = new non_ho_niente_da_mettermiVisitor();
		loader.setErrorReporter(new SemanticErrorHandler());
		loader.visit(tree);
		//resultPrint();

		//Term list = answer.getVarValue("L");
		//Struct listStruct = (Struct)list;
		//listStruct.

	}

}
